
package states{
	
	import org.flixel.plugin.photonstorm.FlxCollision;
	import environment.Hospital;
	import character.Human;
	import character.Car;
	import events.GameEventManager;
	import events.GameEvent;
	import org.flixel.*;
	
	public class GamePlayState extends FlxState {
		
		[Embed(source="../img/grassroad.png")] private var GrassRoad : Class;
		[Embed(source="../img/explo.png")] private var Explode : Class;
		[Embed(source="../img/infect.png")] private var InfectFX:Class;
		[Embed(source="../img/hello.png")] private var Hello:Class;
		
		[Embed(source="../img/monsterlaser.png")] private var MonsterLaser : Class;
		[Embed(source="../img/monsterlaser_gul.png")] private var MonsterLaserYellow : Class;
		
		[Embed(source="../mp3/zombie.mp3")]
		private var Musix : Class;
		
		[Embed(source="../mp3/welcome.mp3")]
		private var Welcome : Class;
		
		private static var _instance:GamePlayState;
		
		public static function needToBeFetched(human:Human):void {
			_instance._humansInNeedOfFetching.push(human);	
		}
		
		public static function fireClick():void {
			_instance._fireType = _instance._niloCursor.getAction();
		}
		
		public static function rainFire(numberOfFireBalls:int):void {
			_instance.rainFire(numberOfFireBalls);
		}
		
		public static function killSprite(sprite:FlxSprite):void {
			_instance.killSprite(sprite);
		}
		
		public static function doSFX(X:Number, Y:Number, type:String, cb:Function = null):void{
			_instance.doSFX(X, Y, type, cb);
		}
		
		public static function get hospital():Hospital {
			return _instance._hospital;
		}
		
		public static function get tileEffects():FlxGroup {
			return _instance.tileeffects;
		}
		
		public static function get tileEffects2():FlxGroup {
			return _instance.tileeffects2;
		}
		
		public static function get tileColliders():FlxGroup {
			return _instance.tilecolliders;
		}
		
		public static function get monsterColliders():FlxGroup {
			return _instance.monstercolliders;
		}
		
		public static function get monsters():FlxGroup {
			return _instance.monsters;
		}
		
		public static function get gameEvents():GameEventManager {
			return _instance.gameEvents;
		}
		
		public static function applyScore(type:String, X:Number, Y:Number):void {
			if (_instance._introDone){
				var score:Number = 0;
				if (type == "human"){
					score = -20;
				}
				else if (type == "humanhitbycar"){
					score = -10;
				}
				else if (type =="humandevoured"){
					score = -45;
				}
				else if (type == "car"){
					score = -15;
				}
				else if (type == "hospital"){
					score = 30;
				}
				var flxText:FlxText = new FlxText(X, Y, 100, (score > 0 ? "+" : "") + score);
				flxText.size = 20;
				flxText.color = score < 0 ? 0xffff0000 : 0xff00ff00;
				flxText.velocity.y = -80;
				flxText.flicker(1.2);
				GamePlayState._instance.add(flxText);
				gameEvents.add(new GameEvent(function(gameEvent:GameEvent):void{
					_instance.gameEvents.remove(gameEvent);
					_instance.remove(flxText);
				}, 1.2));
				_instance._score += score;
			}
		}
		
		private var bounds:FlxObject;
		private var frontLayer:FlxGroup, middleLayer:FlxGroup, monsters:FlxGroup, backLayer:FlxGroup, uiLayer:FlxGroup;
		public var humans:FlxGroup, cars:FlxGroup, tileeffects:FlxGroup, tileeffects2:FlxGroup, tilecolliders:FlxGroup, sfx:FlxGroup, monstercolliders:FlxGroup;
		
		//	Collision groupings:
		public var humansAndCars:FlxGroup;
		
		
		public static function get score():Number {
			return _instance._score;
		}
		public static function get instance():GamePlayState {return FlxG.state as GamePlayState;}
		public static function get movementSpeed():Number {return 20;}
	
		public var gameEvents:GameEventManager;
		
		public var scoreMode:String;
		
		public var tilemap:FlxTilemap;
		
		private var _score:Number = 0;
		
		private var _hospital:Hospital;
		private var _highlight:FlxSprite;
		private var _niloCursor:NiloCursor;
		private var _fireType:String;
		private var _tilesheet:Array;
		
		private var _currentCarAmt:int;
		private var _currentHumanAmt:int;
		private var _humansInNeedOfFetching:Array = new Array();
		
		private var _timer:Number = 90;
		private var _introDone:Boolean, _canClick:Boolean;
		
		//	Constants:
		public var carSpeed:Number = 350;
		public var carSpawnRate:Number = 6.5;
		public var carSpawnRateVariation:Number = 12;
		public var maxCarAmt:int = 10;
		public var humanSpeed:Number = 40;
		public var hospitalSpeed:Number = 100;
		public var zombieSpeed:Number = 60;
		public var humanBurnSpeed:Number = 130;
		public var humanSpawnRate:Number = .5;
		public var humanSpawnRateVariation:Number = 1.5;
		public var maxHumanAmt:int = 45;
		
		override public function create():void {
			super.create();
			_instance = this;
			NiloTile.reset();
			_introDone = false;
			_canClick = false;
			//	Set bounds:
			bounds = new FlxObject(-10, -10, FlxG.width + 10, FlxG.height + 10);
			
			//	Create the tile array:
			var tiledata:Array = 
			[
			1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1,
			1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1,
			3, 3, 3, 4, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 3,
			1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1,
			1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1,
			1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 5, 1, 2, 1, 1, 1,
			1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1,
			1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1,
			3, 3, 3, 4, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 3,
			1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1,
			1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1,
			];
			
			
			tilemap = new FlxTilemap();
			tilemap.loadMap(FlxTilemap.arrayToCSV(tiledata, 16), GrassRoad);
			
			FlxG.bgColor = 0xffffffff;
			
			frontLayer = new FlxGroup();
			middleLayer = new FlxGroup();
			backLayer = new FlxGroup();
			uiLayer = new FlxGroup();
			
			backLayer.add(tilemap);
			
			// Optional collider groups:
			humans = new FlxGroup();
			cars = new FlxGroup();
			
			
			tileeffects = new FlxGroup();
			tileeffects2 = new FlxGroup();
			tilecolliders = new FlxGroup();
			monstercolliders = new FlxGroup();
			sfx = new FlxGroup();
			
			monsters = new FlxGroup();
			tilecolliders.add(monstercolliders);
			//middleLayer.add(tilecolliders);
			
			//Do not add this group to the renderer
			humansAndCars = new FlxGroup();
			humansAndCars.add(humans);
			humansAndCars.add(cars);
			//humansAndCars.add(monstercolliders);
			
			//	Add events:
			gameEvents = new GameEventManager();
			add(gameEvents);
			
			//	Add layers:
			add(backLayer);
			add(middleLayer);
			add(humans);
			add(cars);
			add(monsters);
			add(frontLayer);
			add(uiLayer);
			add(sfx);
			_niloCursor = new NiloCursor();
			_fireType = null;
			add(_niloCursor);
			
			
			//	Add other stuff:
			_highlight = new FlxSprite();
			_highlight.makeGraphic(50, 50, 0x88ffffff);
			_highlight.visible = false;
			//uiLayer.add(_highlight);
			
			_hospital = new Hospital();
			middleLayer.add(_hospital);
			middleLayer.add(tileeffects);
			frontLayer.add(_hospital.hospitalTilemapLayerAbove);
			frontLayer.add(tileeffects2);
			
			_tilesheet = NiloTile.tilesheet;
			for (var i:uint = 0; i < _tilesheet.length; i++){
				if (tiledata[i] == 2 || tiledata[i] == 3 || tiledata[i] == 4){
					(_tilesheet[i] as NiloTile).tileType = "road";
				}
			}
			
			//	Game events:
			/*
			gameEvents.add(new GameEvent(function (gameEvent:GameEvent):void{
				var tile:NiloTile = _tilesheet[_counter];
				_highlight.x = tile.x;
				_highlight.y = tile.y;
				_counter++;
				_counter = (_counter % _tilesheet.length);	
			}, .2, null, -1));
			*/
			//	Setup human spawning:
			setupHumanSpawn();
			
			//	Car spawn loop 1:
			gameEvents.add(new GameEvent(function(gameEvent:GameEvent):void{
				gameEvent.timedelay = carSpawnTime;
				var car:Car = cars.getFirstDead() as Car;
				if (car == null) {
					car = new Car();
					cars.add(car);
				}
				
				if ((FlxG.random() * 10) > 5){
					car.reset(150, 0);
				}
				else {
					car.reset(FlxG.width - 150, 0);
				}
				car.setGFX("DOWN");
				car.velocity.y = carSpeed;
				_currentCarAmt++;
				FlxG.play(AssetStore.Car, .2);
			}, carSpawnTime, canSpawnCar, -1));
			
			//	Car spawn loop 2:
			gameEvents.add(new GameEvent(function(gameEvent:GameEvent):void{
				gameEvent.timedelay = carSpawnTime;
				var car:Car = cars.getFirstDead() as Car;
				if (car == null) {
					car = new Car();
					cars.add(car);
				}
				
				if ((FlxG.random() * 10) > 5){
					car.reset(175, FlxG.height - car.height);
				}
				else {
					car.reset(FlxG.width - 75, FlxG.height - car.height);
				}
				car.setGFX("UP");
				car.velocity.y = -carSpeed;
				_currentCarAmt++;
				FlxG.play(AssetStore.Car, .2);
			}, carSpawnTime, canSpawnCar, -1));
			
			//	Car spawn loop 3:
			gameEvents.add(new GameEvent(function(gameEvent:GameEvent):void{
				gameEvent.timedelay = carSpawnTime;
				var car:Car = cars.getFirstDead() as Car;
				if (car == null) {
					car = new Car();
					cars.add(car);
				}
				if ((FlxG.random() * 10) > 5){
					car.reset(0, 100);
				}
				else {
					car.reset(0, FlxG.height - 100);
				}
				car.setGFX("RIGHT");
				car.velocity.x = carSpeed;
				_currentCarAmt++;
				FlxG.play(AssetStore.Car, .2);
			}, carSpawnTime, canSpawnCar, -1));
			
			//	Car spawn loop 4:
			gameEvents.add(new GameEvent(function(gameEvent:GameEvent):void{
				gameEvent.timedelay = carSpawnTime;
				var car:Car = cars.getFirstDead() as Car;
				if (car == null) {
					car = new Car();
					cars.add(car);
				}

				if ((FlxG.random() * 10) > 5){
					car.reset(FlxG.width - car.width, 125);
				}
				else {
					car.reset(FlxG.width - car.width, FlxG.height - 75);
				}
				car.setGFX("LEFT");
				car.velocity.x = -carSpeed;
				_currentCarAmt++;
				FlxG.play(AssetStore.Car, .2);
			}, carSpawnTime, canSpawnCar, -1));
			
			//Create an initial fade:
			add(new NiloFader().executeFade(true, 1, function(fader:NiloFader):void{
				remove(fader);
				
				var text:FlxText = new FlxText(60, 40, 500);
				text.size = 25;
				var niloGroup:NiloGroup = new NiloGroup();
				niloGroup.fade(0.0, 1);
				var sprite:FlxSprite = new FlxSprite(0, 0, Hello);
				sprite.visible = false;
				add(niloGroup);
				niloGroup.add(sprite);
				
				add(text);
				
				gameEvents.queue(new GameEvent(function(gameEvent:GameEvent):void{
					remove(gameEvent);
					niloGroup.fade(1.0, .4);
					sprite.visible = true;
				}, 5));
				gameEvents.queue(new GameEvent(function(gameEvent:GameEvent):void{
					remove(gameEvent);
					FlxG.play(Welcome, 2.0);
					text.text = "Welcome to earth, commander!";
				}, 2));
				gameEvents.queue(new GameEvent(function(gameEvent:GameEvent):void{
					remove(gameEvent);
					text.text = "Welcome to earth, commander!\nLet us help these poor people!";
				}, 2.3));
				gameEvents.queue(new GameEvent(function(gameEvent:GameEvent):void{
					remove(gameEvent);
					text.text = "First, try to upgrade the hospital.";
				}, 4.8));
				gameEvents.queue(new GameEvent(function(gameEvent:GameEvent):void{
					remove(gameEvent);
					remove(text);
					niloGroup.fade(0.0, .4);
					_canClick = true;
					_niloCursor.fullCharge();
				}, 3));
				gameEvents.queue(new GameEvent(function(gameEvent:GameEvent):void{
					remove(niloGroup);
					FlxG.playMusic(Musix, .2);
					_introDone = true;
				}, 0, function():Boolean {return _niloCursor.initialPress}));
			}));
			
			gameEvents.add(new GameEvent(defeat, 0, _hospital.isCompletelyDestroyed));
			gameEvents.add(new GameEvent(victory, 0, function():Boolean{
				return _timer <= 0;
			}));
		}
		
		public function get carSpawnTime():Number{
			return carSpawnRate + (carSpawnRateVariation * FlxG.random());
		}
		
		public function get humanSpawnTime():Number{
			return humanSpawnRate + (humanSpawnRateVariation * FlxG.random());
		}
		
		public function canSpawnCar():Boolean {
			return _introDone && _currentCarAmt < maxCarAmt;
		}
		
		public function canSpawnHuman():Boolean {
			return _currentHumanAmt < maxHumanAmt;
		}
		
		public function shouldSpawnHospitalWorker():Boolean {
			return _humansInNeedOfFetching.length > 0 && _hospital.canSpawnMore;
		}
		
		public function setupHumanSpawn():void {
			//	Normal human spawns:
			gameEvents.add(new GameEvent(function(gameEvent:GameEvent):void{
				var spawnX:Number = 0;
				var spawnY:Number = 0;
				var veloX:Number = 0;
				var veloY:Number = 0;
				if (NiloUtils.randomBoolean()){
					spawnX = NiloUtils.randomBoolean() ? 0 : FlxG.width;
					spawnY = (FlxG.random() * FlxG.height);
					veloX = spawnX == 0 ? humanSpeed : -humanSpeed;
				}
				else {
					spawnY = NiloUtils.randomBoolean() ? 0 : FlxG.height;
					spawnX = (FlxG.random() * FlxG.width);
					veloY = spawnY == 0 ? humanSpeed : -humanSpeed;
				}
				var human:Human = humans.getFirstDead() as Human;
				if (human == null){
					human = new Human();
					humans.add(human);
				}
				human.speed = humanSpeed;
				human.burningSpeed = humanBurnSpeed;
				human.zombieSpeed = zombieSpeed;
				human.reset(spawnX, spawnY);
				human.velocity.x = veloX;
				human.velocity.y = veloY;
				human.initializeAs("Regular");
				gameEvent.timedelay = humanSpawnTime;
				_currentHumanAmt++;
			}, humanSpawnTime, canSpawnHuman, -1));
			
			//	Hospital human spawns:
			/*
			gameEvents.add(new GameEvent(function():void{
				var human:Human = humans.getFirstDead() as Human;
				if (human == null){
					human = new Human();
					humans.add(human);
				}
				human.speed = hospitalSpeed;
				human.burningSpeed = humanBurnSpeed;
				var humanNeedsHelp:Human = _humansInNeedOfFetching.pop();
				var point:FlxPoint = _hospital.closestEntrance(humanNeedsHelp);
				human.reset(point.x, point.y);
				human.initializeAs("Hospital");
				human.goHelp(humanNeedsHelp);
				_hospital.addWorkerToField();
			}, 1, shouldSpawnHospitalWorker, -1));
			 * */
		}
		
		//	Update loop:
		override public function update():void {
			
			try{
				super.update();
				if (_introDone){
					_timer -= FlxG.elapsed;
				}
			//	Move everything:
			boundsCheckSprites(cars);
			boundsCheckSprites(humans);
			boundsCheck(monstercolliders);
			FlxG.collide(cars, cars, collisionHandlerCars);
			FlxG.overlap(cars, humans, collisionHandlerCarsAndHumans);
			FlxG.collide(_hospital, humans, collisionHumansHospital);
			FlxG.overlap(monstercolliders, _hospital, collisionMonsterHospital);
			FlxG.collide(_hospital, cars);
			FlxG.overlap(humansAndCars, tilecolliders, collisionHandler);
			
			//FlxG.overlap(monstercolliders, tilecolliders, collisionHandler);
			
			
			var tile:NiloTile = NiloTile.getTileAt(FlxG.mouse.x, FlxG.mouse.y);
			_highlight.x = tile.x;
			_highlight.y = tile.y;
			if (FlxG.mouse.justPressed() && _canClick){
				fireClick();
			}	
			if (_fireType != null){
				tile.infuseTileWith(_fireType);
				if (_fireType == "explode"){
					FlxG.shake(.004, 0.2);
					doSFX(tile.x, tile.y, "explode");
				}
				_fireType = null;
			}
			}catch(error:Error){
				FlxG.log(error);	
			}
			
		}
		
		//	Simple collision handler:
		public function collisionHandler(object1:FlxSprite, object2:FlxSprite):void{
			if (FlxCollision.pixelPerfectCheck(object1, object2)){
				if (object1 is Collider){
					(object1 as Collider).collidedWith(object2);
				}
				if (object2 is Collider){
					(object2 as Collider).collidedWith(object1);
				}
			}
		}
		
		//	Collision handler, cars:
		public function collisionHandlerCars(object1:Car, object2:Car):void{
			var tile:NiloTile = null;
			if (!object1.wrecked){
				tile = NiloTile.getTileAt(object1.x, object1.y);
				tile.infuseTileWith("fire");
			}
			if (!object2.wrecked){
				tile = NiloTile.getTileAt(object2.x, object2.y);
				tile.infuseTileWith("fire");
			}
			//FlxG.shake(0.003, 0.2, null, false);
			object1.crash();
			object2.crash();
			object1.decellerateToZero(1);
			object2.decellerateToZero(1);
		}
		
		//	Collision handler, cars and humans:
		public function collisionHandlerCarsAndHumans(object1:FlxSprite, object2:FlxSprite):void{
			if (FlxCollision.pixelPerfectCheck(object1, object2)){
				var human:Human = (object1 is Human) ? object1 as Human : object2 as Human;
				var car:Car = (object1 is Car) ? object1 as Car : object2 as Car;
				if (!(car.velocity.x == 0 && car.velocity.y == 0)){
					if (!human.isInjured){
						if (human.isInfested){
							if (!car.wrecked){
								car.crash();
							}
						}
						else {
							human.velocity.x = car.velocity.x;
							human.velocity.y = car.velocity.y;	
							human.runOver();
						}
					}
				}
				else {
					car.immovable = true;
					FlxObject.separate(car, human);
					if (human.type == "Hospital"){
						human.refreshCourse();
					}
				car.immovable = false;
				}
			}
		}
		
		public function collisionHumansHospital(object1:FlxObject, object2:FlxObject):void{
			var human:Human = null;
			if (object1 is Human){
				human = object1 as Human;
				//object2.flicker(.1);
			}
			else {
				human = object2 as Human;
				//object1.flicker(.1);
			}
			if (human.isInfested){
				human.moveTowardsHospital();
				_hospital.damage(human.damagePerSecond * FlxG.elapsed);
			}
		}
		
		public function collisionMonsterHospital(object1:FlxObject, object2:FlxObject):void{
			var collider:Collider = null;
			if (object1 is Collider){
				collider = object1 as Collider;
			}
			else {
				collider = object2 as Collider;
			}
			collider.parent.bounce(collider, object2);
		}
		
		//	Bounds check:
		public function boundsCheck(group:FlxGroup):void{
			for (var i:uint = 0; i < group.members.length; i++){
				if (group.members[i] && group.members[i] is Collider){
					var collider:Collider = group.members[i] as Collider;
					if (!FlxG.overlap(collider, bounds)){
						collider.outOfBounds();
					}
				}
			}
		}
		
		//	Bounds check for sprites:
		public function boundsCheckSprites(group:FlxGroup):void{
			for (var i:uint = 0; i < group.members.length; i++){
				if (group.members[i] && group.members[i] is FlxSprite){
					var sprite:FlxSprite = group.members[i] as FlxSprite;
					if (sprite.alive && !FlxG.overlap(sprite, bounds)){
						killSprite(sprite);
					}
				}
			}
		}
		
		public function killSprite(sprite:FlxSprite):void {
			sprite.kill();
			if (sprite is Human) {
			if ((sprite as Human).type == "Regular")
				_currentHumanAmt--;
			}
			else {
				_hospital.removeWorkerFromField();
			}
			if (sprite is Car) _currentCarAmt--;
		}
		
		public function rainFire(numberOfFireBalls:int):void{
			var exludedTiles:Array = [];
			var numberOfTiles:uint = NiloTile.totalTiles;
			var i:uint = 0;
			for (i = 0; i < numberOfFireBalls; i++){
				var randomNumber:uint = Math.floor((numberOfTiles - exludedTiles.length) * FlxG.random());
				var reIterate:Boolean = false;
				do {
					reIterate = false;
					for (var j:uint = 0; j < exludedTiles.length; j++){
						if (exludedTiles[j] == randomNumber) {
							reIterate = true;
							randomNumber = (randomNumber + 1) % numberOfTiles;
							break;
						}
					}
				}while (reIterate);
				exludedTiles.push(randomNumber);
			}
			for (i = 0; i < exludedTiles.length; i++){
				//	TODO: Add fireball animation
				(_tilesheet[exludedTiles[i]] as NiloTile).infuseTileWith("fire");
			}
		}
		
		
		
		public function doSFX(X:Number, Y:Number, type:String, cb:Function = null):void {
			var fx:FlxSprite = new FlxSprite();
			var frames:uint = 0;
			if (type == "explode"){
				fx.x = X - 15;
				fx.y = Y - 206;
				fx.loadGraphic(Explode, true, false, 96, 256);
				fx.addAnimation("explosion", [0, 1, 2, 3, 4, 5, 5], 10, false);
				sfx.add(fx);
				frames = 5;
				fx.play("explosion");
			}
			else if (type == "monstersend"){
				
			}
			else if (type == "monsterrecieve"){
				
			}
			fx.addAnimationCallback(function(name:String, frameN:Number, frameI:Number):void{
				if (frameN > frames) {
					sfx.remove(fx);
					if (cb != null) cb();
				}
			});
		}
		
		public function defeat():void{
			FlxG.music.fadeOut(2);
			add(new NiloFader().executeFade(false, 1, function():void{
				FlxG.switchState(new EndState());
			}));
		}
		
		public function victory():void {
			FlxG.music.fadeOut(2);
			add(new NiloFader().executeFade(false, 1, function():void{
				FlxG.switchState(new EndState());
			}));
		}
	}
}

